﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Jobping.StickyBeak
{
	public abstract class FileRecorder : ILogRepository
	{
		private const char Seperator = '\t';
		private const char EscapeCharacter = '\\';
		public static readonly string DateTimeFormat = "yyyy-MM-dd HH:mm:ss:ff";

		public void Record(RequestLog request)
		{
			StringBuilder sb = new StringBuilder();
			WriteValue("2", sb);

			WriteValue(request.DateUtc.ToString(DateTimeFormat), sb);
			WriteValue(request.HttpMethod, sb);
			WriteValue(request.Url, sb);
			WriteValue(request.Referral, sb);
			WriteValue(request.IPAddress, sb);
			WriteValue(request.Username, sb);
			WriteValue(request.Id, sb);
			WriteValue(request.BrowserId, sb);
			WriteValue(request.SessionId, sb);

			//Header
			WritePairs(request.HeaderValues, sb);
			sb.Append(Seperator);

			//QueryString
			WritePairs(request.QueryStringValues, sb);
			sb.Append(Seperator);

			//Forms
			WritePairs(request.FormValues, sb);
			sb.Append(Seperator);

			//Cookies
			WritePairs(request.CookieValues, sb);
			sb.Append(Seperator);

			//Cookies
			WritePairs(request.SessionValues, sb);
			sb.Append(Seperator);

			this.WriteRequest(sb.ToString());
		}

		public abstract IEnumerable<string> GetPages();

		public abstract IEnumerable<RequestLog> GetPage(string pageId);
		
		public abstract void WriteRequest(string requestItem);

		protected IEnumerable<RequestLog> Parse(FileInfo f)
		{
			using (StreamReader reader = f.OpenText())
			{
				while (reader.Peek() > 0)
				{
					while (Char.IsWhiteSpace((char)reader.Peek()))
						reader.Read();

					if (reader.Peek() > 0)
						yield return Parse(reader);
				}
				reader.Close();
			}
		}

		private RequestLog Parse(StreamReader reader)
		{
			RequestLog log = new RequestLog();

			string version = ReadValue(reader);
			int versionNo = 0;
			Int32.TryParse(version, out versionNo);

			log.DateUtc = DateTime.ParseExact(ReadValue(reader), DateTimeFormat, System.Globalization.CultureInfo.CurrentCulture);
			log.HttpMethod = ReadValue(reader);

			log.Url = ReadValue(reader);
			log.Referral = ReadValue(reader);
			log.IPAddress = ReadValue(reader);
			log.Username = ReadValue(reader);
			log.Id = ReadValue(reader);
			log.BrowserId = ReadValue(reader);
			log.SessionId = ReadValue(reader);

			//Header
			log.HeaderValues = ReadPairs(reader, RequestLogValueSource.Header);

			//QueryString
			log.QueryStringValues = ReadPairs(reader, RequestLogValueSource.QueryString);

			//Forms
			log.FormValues = ReadPairs(reader, RequestLogValueSource.Form);

			//Cookies
			log.CookieValues = ReadPairs(reader, RequestLogValueSource.Cookie);

			//Session (optional)
			if (versionNo >= 2)
				log.SessionValues = ReadPairs(reader, RequestLogValueSource.Session);
			else
				log.SessionValues = new RequestLogValue[0];
			return log;
		}

		private string ReadValue(StreamReader reader)
		{
			StringBuilder sb = new StringBuilder();

			int escapeCount = 0;
			bool isEscaped = false;
			char current;

			while (true)
			{
				current = (char)reader.Read();

				if (current == Seperator && !isEscaped)
					break;

				if (current == EscapeCharacter && !isEscaped)
				{
					isEscaped = true;
					escapeCount += 1;
					continue;
				}

				isEscaped = false;

				sb.Append(current);

				if (reader.Peek() < 0)
					break;
			}

			return sb.ToString().Trim();
		}

		private RequestLogValue[] ReadPairs(StreamReader reader, RequestLogValueSource source)
		{
			List<RequestLogValue> logValues = new List<RequestLogValue>();

			RequestLogValue logValue = null;

			while ((logValue = ReadLogValue(reader, source)) != null)
				logValues.Add(logValue);

			return logValues.ToArray();
		}

		private RequestLogValue ReadLogValue(StreamReader reader, RequestLogValueSource source)
		{
			string key = ReadValue(reader);

			if (String.IsNullOrEmpty(key))
				return null;

			string value = ReadValue(reader);

			return new RequestLogValue() { Key = key, Value = value, Source = source };
		}

		private static void WritePairs(RequestLogValue[] pairs, StringBuilder sb)
		{
			for (int i = 0; i < pairs.Length; i++)
			{
				if (pairs[i].Key == null) continue;

				WriteValue(pairs[i].Key, sb);
				WriteValue(pairs[i].Value, sb);
			}
		}

		private static void WriteValue(string value, StringBuilder sb)
		{
			if (value == null)
			{
				sb.Append(Seperator);
				return;
			}

			foreach (var c in value)
			{
				if (c == EscapeCharacter || c == Seperator)
					sb.Append(EscapeCharacter);

				sb.Append(c);
			}
			sb.Append(Seperator);
		}
	}
}
